/*******************************************************************************
 * Copyright 2009 DCSpectrometer - http://code.google.com/p/dcspectrometer 
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *     
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 *******************************************************************************/
package com.dcspectrometer.Analysis;

/**
 * This class represents a Simulation Manager that performs some IExperiment as many times as required,
 * to make sure that the average result is within the required tolerance with required probability.
 */
public class SimulationManager {
	private IExperiment experiment = null; // Experiment to be performed 
	private double tolerance = Double.NaN; // tolerance to be observed
	private double probability = Double.NaN; // probability that tolerance is observed
	
	/**
	 * Constructor that creates a SimulationManager for experiment 
	 * with tolerance and probability defined.
	 * 
	 * @param experiment - experiment to be performed
	 * @param tolerance - tolerance to be observed
	 * @param probability - probability to be observed
	 */
	public SimulationManager(IExperiment experiment, double tolerance, double probability) {
		this.experiment = experiment;
		this.tolerance = tolerance;
		this.probability = probability;
	}
	
	/**
	 * Returns the estimated average of the experiment.
	 * Estimated with probability and tolerance defined in constructor.
	 * 
	 * @return
	 */
	public double estimate() {
		if(experiment == null) return Double.NaN;
		
		// Observe experiment Mean and Variance
		Mean experimentMean = new Mean();
		Variance experimentVariance = new Variance();
		double nextExperiment = 0;
		// Number of experiments performed
		double n = 0;
		// Current experiment mean tolerance. 
		double curTolerance = 0;
		
		// Find Z
		double Z = getZ(probability);
		
		while(true) {
			// Obtain new value
			nextExperiment = experiment.makeExperiment();
			// Update mean and variance
			experimentMean.addValue(nextExperiment);
			experimentVariance.addValue(nextExperiment);
			// Increment number of experiments
			n++;

			if(n<3) continue;
			
			// Check against required tolerance
			curTolerance = Z*Math.sqrt(experimentVariance.getResults()/n);
			if(curTolerance < tolerance) {
				/*
				System.out.println("N = " + n);
				System.out.println("curTolerance = " + curTolerance);
				System.out.println("Mean = " + experimentMean.getResults());
				System.out.println("Variance = " + experimentVariance.getResults());
				*/
				return experimentMean.getResults();
			}
/*
			if(n % 1000000 == 0) {
				System.out.println("N = " + n);
				System.out.println("curTolerance = " + curTolerance);
				System.out.println("Mean = " + experimentMean.getResults());
				System.out.println("Variance = " + experimentVariance.getResults());
			}
*/
		}
		
		//return Double.NaN;
	}

	/**
	 * The function returns the appropriate Z for a given probability.
	 * @param probability
	 * @return
	 */
	private double getZ(double probability) {
		// This method is not working correctly.
		double X = 0;
		double fX = 0;
		double fXPrime = 0;
		double boundaryValue = 1E-10;
		double intersection = (1-probability)/2;
		
		do {
			fX = (1 - Norm5PointCDF.getCDF(X)) - (intersection);
			fXPrime = -Norm5PointCDF.getCDFPrime(X);
			X -= (fX/fXPrime);
//			System.out.println("X = " + X + " fX = " + fX);
		} while(Math.abs(fX) > boundaryValue);
		return X;
	}	
}